#ifndef UNIFIED_MATH_FUNCTIONS_HPP
#define UNIFIED_MATH_FUNCTIONS_HPP

#include "macros.hpp"
#include <Eigen/Dense>

namespace hms
{

/**
 * @brief Raises a base to a power.
 * 
 * @tparam Base Accepts arithmetic and Eigen::DenseBase type values.
 * @tparam Exp Accepts arithmetic types
 * @param base 
 * @param exp 
 * @return decltype(auto) 
 */
template<typename Base, typename Exp>
auto pow( const Base& base, const Exp& exp ) -> decltype(auto) {
	if constexpr ( std::is_base_of_v<Eigen::DenseBase<Base>, Base> ){
		return base.pow(exp);
	} else {
		static_assert( std::is_arithmetic_v<Base> );
		static_assert( std::is_arithmetic_v<Exp>  );
		return std::pow(base, exp);
	}
}

/**
 * @brief Returns the square of the argument.
 * 
 * @param t Accepts arithmetic and Eigen::DenseBase type values.
 * @return decltype(auto) For arithmetic types, the return type is the same
 * as the argument type. For Eigen::DenseBase types, it's the return type of
 * Eigen::square(const T&).
 */
template<typename T>
auto square( const T& t ) -> decltype(auto) {
	if constexpr ( std::is_base_of_v<Eigen::DenseBase<T>, T> ){
		return Eigen::square(t);
	} else {
		static_assert( std::is_arithmetic_v<T> );
		return T{t*t};
	}
}

/**
 * @brief Returns the cube of the argument.
 * 
 * @param t Accepts arithmetic and Eigen::DenseBase type values.
 * @return decltype(auto) For arithmetic types, the return type is the same
 * as the argument type. For Eigen::DenseBase types, it's the return type of
 * Eigen::cube(const T&).
 */
template<typename T>
auto cube( const T& t ) -> decltype(auto) {
	if constexpr ( std::is_base_of_v<Eigen::DenseBase<T>, T> ){
		return Eigen::cube(t);
	} else {
		static_assert( std::is_arithmetic_v<T> );
		return T{t*t*t};
	}
}



/**
 * @brief Returns true if the argument is greater or equal to zero and false
 * otherwise.
 * 
 * @param t Accepts arithmetic and Eigen::DenseBase type values.
 * 
 * For Eigen types, only returns true if each coefficient is >= zero.
 */
template<typename T>
bool notNegative( const T& t ){
	if constexpr ( std::is_base_of_v<Eigen::DenseBase<T>, T> ){
		return (t.array() >= 0).all();
	} else {
		static_assert( std::is_arithmetic_v<T> );
		return t >= 0;
	}
}

/**
 * @brief Returns true if the argument is non-zero and false otherwise.
 * 
 * @param t Accepts arithmetic and Eigen::DenseBase type values.
 * 
 * For Eigen types, only returns true if each coefficient is non-zero.
 */
template<typename T>
bool nonZero( const T& t ){
	if constexpr ( std::is_base_of_v<Eigen::DenseBase<T>, T> ){
		return (t.array() != 0).all();
	} else {
		static_assert( std::is_arithmetic_v<T> );
		return t != 0;
	}
}


/**
 * @brief Chains to Eigen::min() or std::min() depending on parameter type.
 * 
 * @tparam A type of first parameter
 * @tparam B type of second parameter
 * @param a can be arithmetic or Eigen::DenseBase type
 * @param b can be arithmetic or Eigen::DenseBase type
 * @return decltype(auto)
 * For arithmetic types, return type is std::common_type<A,B>.
 * If A is an Eigen type, or both A and B are Eigen types,
 * return type is that of Eigen::DenseBase<A>::min(B).
 * If only B is an Eigen type,
 * return type is that of Eigen::DenseBase<B>::min(A) 
 */
template<typename A, typename B>
auto min(const A& a, const B& b) -> decltype(auto) {
	if constexpr ( std::is_base_of_v<Eigen::DenseBase<A>, A> ){
		return a.min(b);
	} else if constexpr ( std::is_base_of_v<Eigen::DenseBase<B>, B> ){
		return min(b, a);
	} else {
		return typename std::common_type<A,B>::type{std::min(a, b)};
	}
}

/**
 * @brief Chains to Eigen::max() or std::max() depending on parameter type.
 * 
 * @param a 
 * @param b 
 * @return decltype(auto) 
 */
template<typename A, typename B>
auto max(const A& a, const B& b) -> decltype(auto) {
	if constexpr ( std::is_base_of_v<Eigen::DenseBase<A>, A> ){
		return a.max(b);
	} else if constexpr ( std::is_base_of_v<Eigen::DenseBase<B>, B> ){
		return max(b, a);
	} else {
		return typename std::common_type<A,B>::type{std::max(a, b)};
	}
}

template<typename Derived>
auto sign( const Eigen::DenseBase<Derived>& arg ) -> decltype(auto) {
	return arg.sign();
}

template<typename T>
std::enable_if_t<std::is_arithmetic_v<T>, bool> sign( const T& t ){
	return std::signbit(t);
}

} // namespace hms


#endif